#pragma once

#include <iostream>
#include <string>
#include <memory>


namespace Protocol
{
    class Separator
    {
    public:
        static const std::string prot_separator;
        static const std::string line_separator;
    };
    const std::string Separator::prot_separator = " ";
    const std::string Separator::line_separator = "\r\n";

    // request and response
    class Request
    {
    public:
        virtual ~Request() = default;
        virtual void Debug() = 0;
        virtual bool Serialize(std::string* data) = 0;  // serialize data to string
        virtual bool Deserialize(const std::string& data) = 0;  // deserialize data from string
    };

    class Response
    {
    public:
        virtual ~Response() = default;
        virtual void Debug() = 0;
        virtual bool Serialize(std::string* data) = 0;  // serialize data to string
        virtual bool Deserialize(const std::string& data) = 0;  // deserialize data from string
    };

    // calculate operation
    // final pattern: "len\nx op y\n"  ('\n' is not belong to data)
    // valid payload: "x op y"
    class Cal_Request : public Request
    {
    public:
        Cal_Request() : _x(0), _y(0), _op(0) {}
        Cal_Request(int x, int y, char op) : _x(x), _y(y), _op(op) {}
        void Debug() override
        {
            std::cout << "Cal_Request: " << _x << " " << _y << " " << _op << std::endl;
        }
        bool Serialize(std::string* data) override
        {
            *data = std::to_string(_x) + Separator::prot_separator + _op + Separator::prot_separator + std::to_string(_y);
            return true;
        }
        bool Deserialize(const std::string& data) override
        {
            // "x op y"    [)
            auto left = data.find(Separator::prot_separator);
            if(left == std::string::npos) { return false; }
            auto right = data.rfind(Separator::prot_separator);
            if(right == left) { return false; }

            // data is valid
            int prot_sep_size = Separator::prot_separator.size();
            _x = std::stoi(data.substr(0, left));
            _y = std::stoi(data.substr(right + prot_sep_size));
            std::string op_s = data.substr(left + prot_sep_size, right - (left + prot_sep_size));
            if(op_s.size() != 1) { return false; }
            _op = op_s[0];
            return true;
        }
    public:
        void SetX(int x) { _x = x; }
        void SetY(int y) { _y = y; }
        void SetOp(char op) { _op = op; }
        int GetX() const { return _x; }
        int GetY() const { return _y; }
        char GetOp() const { return _op; }
    private:
        int _x;
        int _y;
        char _op;
    };

    class Cal_Response : public Response
    {
    public:
        Cal_Response() : _result(0), _error_code(0) {}
        Cal_Response(int result) : _result(result), _error_code(0) {}
        void Debug() override
        {
            std::cout << "Cal_Response: " << _result << std::endl;
        }
        bool Serialize(std::string* data) override
        {
            *data = std::to_string(_result) + Separator::line_separator + std::to_string(_error_code);
            return true;
        }
        bool Deserialize(const std::string& data) override
        {
            int sep_pos = data.find(Separator::line_separator);
            if(sep_pos == std::string::npos) { return false; }

            _result = std::stoi(data.substr(0, sep_pos));
            _error_code = std::stoi(data.substr(sep_pos + Separator::line_separator.size()));
            return true;
        }
    public:
        void SetResult(int result) { _result = result; }
        void SetErrorCode(int error_code) { _error_code = error_code; }
        int GetResult() const { return _result; }
        int GetErrorCode() const { return _error_code; }
    private:
        int _result;
        int _error_code;  // 0: success, others: error code
    };

    // encode and decode operation
    // final pattern: "len\ndata\n"
    // valid payload: "data"
    class PacketHandler
    {
    public:
        static std::string Encode(const std::string& data)
        {
            return std::to_string(data.size()) + Separator::line_separator + data + Separator::line_separator;
        }

        // when decode success, delete the first complete packet from data
        static bool Decode(std::string& data, std::string* result)
        {
            int sep_pos = data.find(Separator::line_separator);
            if(sep_pos == std::string::npos) { return false; }

            int len = std::stoi(data.substr(0, sep_pos));
            int total = sep_pos + len + 2 * Separator::line_separator.size();
            if(total > data.size())  // data length must >= one complete packet length
            {
                std::cerr << "total: " << total << "\t data.size(): " << data.size() << std::endl;
                return false;
            }
            *result = data.substr(sep_pos + Separator::line_separator.size(), len);
            data.erase(0, total);  // delete the first complete packet from data
            return true;
        }
    };

    class Cal_Factory
    {
    public:
        // no arg constructor, fill member variables by serilize
        std::shared_ptr<Cal_Request> CreateCalRequest()
        {
            return std::make_shared<Cal_Request>();
        }
        std::shared_ptr<Response> CreateCalResponse()
        {
            return std::make_shared<Cal_Response>();
        }

        // arg constructor
        std::shared_ptr<Cal_Request> CreateCalRequest(int x, int y, char op)
        {
            return std::make_shared<Cal_Request>(x, y, op);
        }
        std::shared_ptr<Response> CreateCalResponse(int result)
        {
            return std::make_shared<Cal_Response>(result);
        }
    };
} // namespace Protocol